﻿//OrderLogHelper.cs
//Copyright (c) 2013 StockSharp LLC, all rights reserved.
//This code module is part of StockSharp library.
//This code is licensed under the GNU GENERAL PUBLIC LICENSE Version 3.
//See the file License.txt for the license details.
//More info on: http://stocksharp.com

namespace StockSharp.Algo
{
	using System;
	using System.Collections;
	using System.Collections.Generic;

	using Ecng.Collections;
	using Ecng.Common;

	using StockSharp.BusinessEntities;

	/// <summary>
	/// Причины отмены заявок в логе заявок.
	/// </summary>
	public enum OrderLogCancelReasons
	{
		/// <summary>
		/// Перерегистрация заявки.
		/// </summary>
		ReRegistered,

		/// <summary>
		/// Отмена заявки.
		/// </summary>
		Canceled,

		/// <summary>
		/// Групповая отмена заявок.
		/// </summary>
		GroupCanceled,

		/// <summary>
		/// Признак удаления остатка заявки по причине кросс-сделки.
		/// </summary>
		CrossTrade,
	}

	/// <summary>
	/// Построение стакана по ордер-логу.
	/// </summary>
	public static class OrderLogHelper
	{
		/// <summary>
		/// Проверить, содержит ли строчка регистрацию заявки.
		/// </summary>
		/// <param name="item">Строчка ордер-лога.</param>
		/// <returns>True, если строчка содержит регистрацию заявки, иначе, false.</returns>
		public static bool IsRegistered(this OrderLogItem item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			return item.Order.State == OrderStates.Active && item.Trade == null;
		}

		/// <summary>
		/// Проверить, содержит ли строчка отменену заявки.
		/// </summary>
		/// <param name="item">Строчка ордер-лога.</param>
		/// <returns>True, если строчка содержит отменену заявки, иначе, false.</returns>
		public static bool IsCanceled(this OrderLogItem item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			return item.Order.State == OrderStates.Done && item.Trade == null;
		}

		/// <summary>
		/// Проверить, содержит ли строчка исполнение заявки.
		/// </summary>
		/// <param name="item">Строчка ордер-лога.</param>
		/// <returns>True, если строчка содержит исполнение заявки, иначе, false.</returns>
		public static bool IsMatched(this OrderLogItem item)
		{
			if (item == null)
				throw new ArgumentNullException("item");

			return item.Trade != null;
		}

		/// <summary>
		/// Получить причину отмены заявки в логе заявок.
		/// </summary>
		/// <param name="item">Строчка ордер-лога.</param>
		/// <returns>Причина отмены заявки в логе заявок.</returns>
		public static OrderLogCancelReasons GetCancelReason(this OrderLogItem item)
		{
			if (!item.IsCanceled())
				throw new ArgumentException("Строчка не является операция отмены заявки.", "item");

			if (item.Order.Status == null)
				throw new ArgumentException("Строчка не содержит информацию о причине отмены заявки.", "item");

			var status = (int)item.Order.Status;

			if (status.HasBits(0x100000))
				return OrderLogCancelReasons.ReRegistered;
			else if (status.HasBits(0x200000))
				return OrderLogCancelReasons.Canceled;
			else if (status.HasBits(0x400000))
				return OrderLogCancelReasons.GroupCanceled;
			else if (status.HasBits(0x800000))
				return OrderLogCancelReasons.CrossTrade;
			else
				throw new ArgumentOutOfRangeException("item", status, "Строчка содержит неверную информацию о причине отмены заявки.");
		}

		private readonly static Dictionary<MarketDepth, OrderLogMarketDepthBuilder> _builders = new Dictionary<MarketDepth, OrderLogMarketDepthBuilder>();

		/// <summary>
		/// Добавить новую строчку из ордер-лога к стакану.
		/// </summary>
		/// <param name="depth">Стакан.</param>
		/// <param name="item">Строчка ордер-лога.</param>
		/// <returns>Был ли изменен стакан.</returns>
		public static bool Update(this MarketDepth depth, OrderLogItem item)
		{
			if (depth == null)
				throw new ArgumentNullException("depth");

			return _builders.SafeAdd(depth, key => new OrderLogMarketDepthBuilder(key)).Update(item);
		}

		private sealed class DepthEnumerable : SimpleEnumerable<MarketDepth>, IEnumerableEx<MarketDepth>
		{
			private sealed class DepthEnumerator : IEnumerator<MarketDepth>
			{
				private readonly TimeSpan _interval;
				private readonly int _maxDepth;
				private readonly IEnumerator<OrderLogItem> _itemsEnumerator;
				private OrderLogMarketDepthBuilder _builder;

				public DepthEnumerator(IEnumerable<OrderLogItem> items, TimeSpan interval, int maxDepth)
				{
					if (items == null)
						throw new ArgumentNullException("items");

					_itemsEnumerator = items.GetEnumerator();
					_interval = interval;
					_maxDepth = maxDepth;
				}

				public MarketDepth Current { get; private set; }

				bool IEnumerator.MoveNext()
				{
					while (_itemsEnumerator.MoveNext())
					{
						var item = _itemsEnumerator.Current;

						if (_builder == null)
							_builder = new OrderLogMarketDepthBuilder(new MarketDepth(item.Order.Security) { MaxDepth = int.MaxValue });

						if (!_builder.Update(item))
							continue;
						
						if (Current != null && (_builder.Depth.LastChangeTime - Current.LastChangeTime) < _interval)
							continue;

						Current = _builder.Depth.Clone();
						Current.MaxDepth = _maxDepth;
						return true;
					}

					Current = null;
					return false;
				}

				public void Reset()
				{
					_itemsEnumerator.Reset();
					_builder = null;
					Current = null;
				}

				object IEnumerator.Current
				{
					get { return Current; }
				}

				void IDisposable.Dispose()
				{
					Reset();
					_itemsEnumerator.Dispose();
				}
			}

			private readonly IEnumerableEx<OrderLogItem> _items;

			public DepthEnumerable(IEnumerableEx<OrderLogItem> items, TimeSpan interval, int maxDepth)
				: base(() => new DepthEnumerator(items, interval, maxDepth))
			{
				if (items == null)
					throw new ArgumentNullException("items");

				if (interval < TimeSpan.Zero)
					throw new ArgumentOutOfRangeException("interval", interval, "Интервал не может быть отрицательным.");

				if (maxDepth < 1)
					throw new ArgumentOutOfRangeException("maxDepth", maxDepth, "Глубина стакана не может быть отрицательной.");

				_items = items;
			}

			int IEnumerableEx<MarketDepth>.Count
			{
				get { return _items.Count; }
			}
		}

		/// <summary>
		/// Построить стаканы из ордер лога.
		/// </summary>
		/// <param name="items">Строчки ордер лога.</param>
		/// <param name="interval">Интервал генерации стакана. По-умолчанаю равен <see cref="TimeSpan.Zero"/>, что означает генерацию стаканов при каждой новой строчке лога заявок.</param>
		/// <param name="maxDepth">Максимальная глубина стакана. По-умолчанию равно <see cref="int.MaxValue"/>, что означает бесконечную глубину.</param>
		/// <returns>Стаканы.</returns>
		public static IEnumerableEx<MarketDepth> ToMarketDepths(this IEnumerableEx<OrderLogItem> items, TimeSpan interval = default(TimeSpan), int maxDepth = int.MaxValue)
		{
			return new DepthEnumerable(items, interval, maxDepth);
		}

		private sealed class TradeEnumerable : SimpleEnumerable<Trade>, IEnumerableEx<Trade>
		{
			private sealed class TradeEnumerator : IEnumerator<Trade>
			{
				private readonly IEnumerator<OrderLogItem> _itemsEnumerator;
				private readonly Dictionary<long, OrderLogItem> _trades = new Dictionary<long, OrderLogItem>(); 

				public TradeEnumerator(IEnumerable<OrderLogItem> items)
				{
					if (items == null)
						throw new ArgumentNullException("items");

					_itemsEnumerator = items.GetEnumerator();
				}

				public Trade Current { get; private set; }

				bool IEnumerator.MoveNext()
				{
					while (_itemsEnumerator.MoveNext())
					{
						var currItem = _itemsEnumerator.Current;

						var trade = currItem.Trade;

						if (trade == null)
							continue;

						var prevItem = _trades.TryGetValue(trade.Id);

						if (prevItem == null)
						{
							_trades.Add(trade.Id, currItem);
						}
						else
						{
							_trades.Remove(trade.Id);

							Current = trade.Clone();
							Current.OrderDirection = prevItem.Order.Direction == OrderDirections.Buy
								? (prevItem.Order.Id > currItem.Order.Id ? OrderDirections.Buy : OrderDirections.Sell)
								: (prevItem.Order.Id > currItem.Order.Id ? OrderDirections.Sell : OrderDirections.Buy);

							return true;
						}
					}

					Current = null;
					return false;
				}

				void IEnumerator.Reset()
				{
					_itemsEnumerator.Reset();
					Current = null;
				}

				object IEnumerator.Current
				{
					get { return Current; }
				}

				void IDisposable.Dispose()
				{
					_itemsEnumerator.Dispose();
				}
			}

			private readonly IEnumerableEx<OrderLogItem> _items;

			public TradeEnumerable(IEnumerableEx<OrderLogItem> items)
				: base(() => new TradeEnumerator(items))
			{
				if (items == null)
					throw new ArgumentNullException("items");

				_items = items;
			}

			int IEnumerableEx<Trade>.Count
			{
				get { return _items.Count; }
			}
		}

		/// <summary>
		/// Построить тиковые сделки из ордер лога.
		/// </summary>
		/// <param name="items">Строчки ордер лога.</param>
		/// <returns>Тиковые сделки.</returns>
		public static IEnumerableEx<Trade> ToTrades(this IEnumerableEx<OrderLogItem> items)
		{
			return new TradeEnumerable(items);
		}
	}
}